import threading

from PyQt5.QtCore import Qt, pyqtSignal, QObject, QThread, QTimer, pyqtSlot
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtWidgets import (
    QWidget, QListWidget, QLineEdit, QVBoxLayout, QHBoxLayout, QFileDialog,
    QLabel, QPushButton, QProgressBar, QTextEdit, QSizePolicy, QComboBox
)

from src.config import *
from src.utils.downloader import sanitize_filename
from src.utils import ensure_placeholder
from src.worker.AudioLinksWorker import AudioLinksWorker
from src.worker.AvatarDownloadWorker import AvatarDownloadWorker
from src.worker.DownloadWorker import DownloadWorker
from src.worker.ServantLoaderWorker import ServantLoaderWorker


class ServantApp(QWidget):
    _threads = []
    _workers = []

    def __init__(self):
        print("Initializing ServantApp...")
        super().__init__()
        self.setWindowTitle("FGO 英灵语音下载器")
        self.resize(1060, 640)
        app_icon_path = os.path.join(BASE_DIR, "app.ico")
        if os.path.exists(app_icon_path):
            self.setWindowIcon(QIcon(app_icon_path))
        self.log_levels = {"DEBUG": 0, "INFO": 1, "WARN": 2, "ERROR": 3}
        self.current_log_level = self.log_levels["INFO"]
        self.voice_dir = VOICE_DIR

        self.servants = []
        self.filtered = []
        self.current = None

        self.avatar_workers = {}
        self.active_downloads = {}
        self.download_lock = threading.Lock()
        self.total_progress = 0
        self.completed_progress = 0

        self._build_ui()

        self._seen_sids = set()
        self._tiamats = []
        self._others = []
        self._sort_order = None
        QTimer.singleShot(0, self.load_servants_threaded)

    def _build_ui(self):
        self.search_edit = QLineEdit()
        self.search_edit.setPlaceholderText("搜索从者名称...")
        self.search_edit.textChanged.connect(self._on_search_changed)

        self.sort_combo = QComboBox()
        self.sort_combo.addItems(["按ID升序", "按ID降序"])
        self.sort_combo.setEnabled(False)  # 仅在全部抓完后可用
        self.sort_combo.currentTextChanged.connect(self._on_sort_changed)

        self.list_widget = QListWidget()
        self.list_widget.currentRowChanged.connect(self._on_select_changed)

        left = QVBoxLayout()
        left.addWidget(self.search_edit)
        left.addWidget(self.sort_combo)
        left.addWidget(self.list_widget)
        left_panel = QWidget()
        left_panel.setLayout(left)
        left_panel.setMinimumWidth(320)

        self.name_label = QLabel("从者名称")
        self.name_label.setStyleSheet("font-size:18px;font-weight:bold;")
        self.avatar_label = QLabel()
        self.avatar_label.setAlignment(Qt.AlignCenter)
        self.avatar_label.setFixedSize(220, 220)
        self.avatar_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        ph = QPixmap(PLACEHOLDER) if os.path.exists(PLACEHOLDER) else ensure_placeholder(220)
        self.avatar_label.setPixmap(ph.scaled(220, 220, Qt.KeepAspectRatio, Qt.SmoothTransformation))

        # --- 下载控制按钮 ---
        self.download_btn = QPushButton("下载语音")
        self.download_btn.clicked.connect(self._on_download_clicked)
        self.cancel_btn = QPushButton("取消下载")
        self.cancel_btn.clicked.connect(self._on_cancel_clicked)
        self.refresh_btn = QPushButton("刷新链接")
        self.refresh_btn.clicked.connect(self._on_refresh_clicked)

        self.download_control_layout = QHBoxLayout()
        self.download_control_layout.addWidget(self.download_btn)
        self.download_control_layout.addWidget(self.cancel_btn)
        self.download_control_layout.addWidget(self.refresh_btn)
        self.cancel_btn.hide()
        self.refresh_btn.hide()
        # --- 结束 ---

        path_label = QLabel("保存位置:")
        self.path_edit = QLineEdit(self.voice_dir)
        self.path_edit.setReadOnly(True)
        browse_btn = QPushButton("浏览...")
        browse_btn.clicked.connect(self._on_browse_clicked)
        path_layout = QHBoxLayout()
        path_layout.addWidget(path_label)
        path_layout.addWidget(self.path_edit)
        path_layout.addWidget(browse_btn)

        self.progress = QProgressBar()
        self.progress.setVisible(False)

        log_level_label = QLabel("日志级别:")
        self.log_level_combo = QComboBox()
        self.log_level_combo.addItems(self.log_levels.keys())
        self.log_level_combo.setCurrentText("INFO")
        self.log_level_combo.currentTextChanged.connect(self._on_log_level_changed)
        log_control_layout = QHBoxLayout()
        log_control_layout.addWidget(log_level_label)
        log_control_layout.addWidget(self.log_level_combo)
        log_control_layout.addStretch()

        self.log_edit = QTextEdit()
        self.log_edit.setReadOnly(True)

        right = QVBoxLayout()
        right.addWidget(self.name_label)
        right.addWidget(self.avatar_label)
        right.addLayout(self.download_control_layout)
        right.addLayout(path_layout)
        right.addWidget(self.progress)
        right.addLayout(log_control_layout)
        right.addWidget(self.log_edit)

        root = QHBoxLayout()
        root.addWidget(left_panel, 0)
        container = QWidget()
        container.setLayout(right)
        root.addWidget(container, 1)
        self.setLayout(root)

    def log(self, text: str):
        try:
            level_str = text.strip()[1:].split(']')[0]
            msg_level = self.log_levels.get(level_str, self.log_levels["INFO"])
        except IndexError:
            msg_level = self.log_levels["INFO"]
        if msg_level >= self.current_log_level:
            self.log_edit.append(text)
            self.log_edit.verticalScrollBar().setValue(self.log_edit.verticalScrollBar().maximum())

    def _on_log_level_changed(self, text: str):
        self.current_log_level = self.log_levels.get(text, self.log_levels["INFO"])
        self.log(f"[INFO] 日志级别已更改为: {text}")

    def _create_and_start_thread(self, worker_class, *args, **kwargs):
        thread = QThread(self)
        worker = worker_class(*args, **kwargs)
        worker.moveToThread(thread)
        worker.log_signal.connect(self.log, Qt.QueuedConnection)
        thread.started.connect(worker.start, Qt.QueuedConnection)
        worker.finished.connect(thread.quit, Qt.QueuedConnection)
        worker.finished.connect(worker.deleteLater, Qt.QueuedConnection)
        thread.finished.connect(thread.deleteLater)
        thread.start()
        self._threads.append(thread)
        self._workers.append(worker)
        return thread, worker

    def load_servants_threaded(self):
        self.progress.setVisible(True)
        self.progress.setRange(0, 0)
        self.log("[DEBUG] 开始加载从者列表...")
        _, worker = self._create_and_start_thread(ServantLoaderWorker)
        # 去重重复连接，并显式使用 QueuedConnection
        worker.servants_updated.connect(self._on_servants_updated, Qt.QueuedConnection)
        worker.finished.connect(lambda: self.progress.setVisible(False), Qt.QueuedConnection)
        worker.finished.connect(lambda: self.sort_combo.setEnabled(True), Qt.QueuedConnection)

    def _on_browse_clicked(self):
        directory = QFileDialog.getExistingDirectory(self, "选择语音保存文件夹", self.voice_dir)
        if directory:
            self.voice_dir = directory
            self.path_edit.setText(self.voice_dir)
            self.log(f"[INFO] 语音保存位置已更新为: {self.voice_dir}")

    def _on_servants_updated(self, servants):
        self._append_page(servants)

    def _refresh_list(self):
        cur_id = self.current[0] if self.current else None
        self.list_widget.blockSignals(True)
        self.list_widget.clear()
        new_selection_index = -1
        for i, (sid, name, _, _) in enumerate(self.filtered):
            self.list_widget.addItem(f"{sid} - {name}")
            if sid == cur_id:
                new_selection_index = i
        self.list_widget.blockSignals(False)
        if new_selection_index != -1:
            self.list_widget.setCurrentRow(new_selection_index)
        elif not self.filtered:
            self._on_select_changed(-1)

    def _on_search_changed(self, text: str):
        t = (text or "").strip().lower()
        self.filtered = [s for s in self.servants if not t or t in s[1].lower() or t in s[0]]
        self._refresh_list()

    def _on_select_changed(self, index: int):
        if index < 0 or index >= len(self.filtered):
            self.current = None
            self.name_label.setText("从者名称")
            ph = QPixmap(PLACEHOLDER) if os.path.exists(PLACEHOLDER) else ensure_placeholder(220)
            self.avatar_label.setPixmap(ph.scaled(220, 220, Qt.KeepAspectRatio, Qt.SmoothTransformation))
            self._update_download_buttons(None)
            return

        self.current = self.filtered[index]
        sid, name, _, icon_url = self.current
        self.name_label.setText(name)
        self._update_download_buttons(sid)

        ph = QPixmap(PLACEHOLDER) if os.path.exists(PLACEHOLDER) else ensure_placeholder(220)
        self.avatar_label.setPixmap(ph.scaled(220, 220, Qt.KeepAspectRatio, Qt.SmoothTransformation))

        safe_name = sanitize_filename(name)
        avatar_path = os.path.join(ICON_DIR, f"{sid}_{safe_name}.png")
        if os.path.exists(avatar_path):
            px = QPixmap(avatar_path)
            if not px.isNull():
                self.avatar_label.setPixmap(px.scaled(220, 220, Qt.KeepAspectRatio, Qt.SmoothTransformation))
            return

        if sid in self.avatar_workers: return
        thread, worker = self._create_and_start_thread(AvatarDownloadWorker, icon_url, avatar_path)
        self.avatar_workers[sid] = worker

        def on_done(path: str, s_id=sid):
            self.avatar_workers.pop(s_id, None)
            if path and os.path.exists(path) and self.current and self.current[0] == s_id:
                px = QPixmap(path)
                if not px.isNull():
                    self.avatar_label.setPixmap(px.scaled(220, 220, Qt.KeepAspectRatio, Qt.SmoothTransformation))

        worker.finished.connect(on_done)

    def _update_download_buttons(self, sid):
        is_downloading = sid is not None and sid in self.active_downloads
        self.download_btn.setVisible(not is_downloading)
        self.cancel_btn.setVisible(is_downloading)
        self.refresh_btn.setVisible(is_downloading)

    def _on_download_clicked(self):
        if not self.current:
            self.log("[WARN] 请选择一个从者")
            return
        self._start_link_fetching(self.current)

    def _on_cancel_clicked(self):
        if not self.current: return
        sid, name, *_ = self.current
        self.log(f"[INFO] 正在取消 {name} 的下载任务...")
        self._cancel_download(sid)

    def _on_refresh_clicked(self):
        if not self.current: return
        sid, name, *_ = self.current
        self.log(f"[INFO] 正在刷新 {name} 的链接并重新下载...")
        self._cancel_download(sid)
        self._start_link_fetching(self.current)

    def _start_link_fetching(self, servant_data):
        sid, name, page_url, _ = servant_data
        with self.download_lock:
            if sid in self.active_downloads:
                self.log(f"[WARN] {name} 的下载任务已在进行中。")
                return
            self.active_downloads[sid] = {}
        self._update_download_buttons(sid)

        thread, worker = self._create_and_start_thread(AudioLinksWorker, page_url, sid, name)
        with self.download_lock:
            self.active_downloads[sid]['links_thread'] = thread
            self.active_downloads[sid]['links_worker'] = worker

        # 显式 QueuedConnection
        worker.links_ready.connect(self._start_downloads, Qt.QueuedConnection)
        worker.finished.connect(lambda s=sid: self.active_downloads.get(s, {}).pop('links_thread', None),
                                Qt.QueuedConnection)

    def _start_downloads(self, links, sid, name):
        with self.download_lock:
            if sid not in self.active_downloads:
                return
            if not links:
                self.log(f"[WARN] {name}: 没有可下载的语音链接")
                self.active_downloads.pop(sid, None)
                if self.current and self.current[0] == sid:
                    self._update_download_buttons(sid)
                return
            self.active_downloads[sid]['file_count'] = len(links)
            self.total_progress += len(links)
            self.progress.setVisible(True)
            self.progress.setRange(0, self.total_progress)
            self.progress.setValue(self.completed_progress)

        out_dir = os.path.join(self.voice_dir, f"{sid}_{sanitize_filename(name)}")
        thread, worker = self._create_and_start_thread(DownloadWorker, links, out_dir)
        with self.download_lock:
            if sid not in self.active_downloads:
                worker.cancel()
                thread.quit()
                return
            self.active_downloads[sid]['dl_thread'] = thread
            self.active_downloads[sid]['dl_worker'] = worker

        # 显式 QueuedConnection；避免直接 start 线程（_create\_and\_start\_thread 已经启动了）
        worker.progress_signal.connect(lambda cur, total, s=sid: self._on_progress_update(s), Qt.QueuedConnection)
        worker.finished.connect(lambda s=sid: self._on_download_finished(s), Qt.QueuedConnection)

    def _on_progress_update(self, sid):
        with self.download_lock:
            if sid not in self.active_downloads: return
            self.completed_progress += 1
            self.progress.setValue(self.completed_progress)

    def _on_download_finished(self, sid):
        with self.download_lock:
            task = self.active_downloads.pop(sid, None)
            if task is None: return

            if not self.active_downloads:
                self.log("[INFO] 所有下载任务完成")
                self.progress.setVisible(False)
                self.total_progress = 0
                self.completed_progress = 0

        if self.current and self.current[0] == sid:
            self._update_download_buttons(sid)

    def _cancel_download(self, sid):
        with self.download_lock:
            task = self.active_downloads.pop(sid, None)
            if not task:
                self.log(f"[DEBUG] 尝试取消一个不存在或已完成的任务: {sid}")
                return

            # 取消链接抓取
            if 'links_worker' in task and (lw := task['links_worker']): lw.cancel()
            if 'links_thread' in task and (lt := task['links_thread']): lt.quit()

            # 取消文件下载
            if 'dl_worker' in task and (dw := task['dl_worker']): dw.cancel()
            if 'dl_thread' in task and (dt := task['dl_thread']): dt.quit()

            # 更新全局进度
            file_count = task.get('file_count', 0)
            if file_count > 0:
                # 假设已完成的无法撤回，只调整总数
                self.total_progress = max(0, self.total_progress - file_count)
                if self.total_progress == 0 or self.completed_progress >= self.total_progress:
                    self.progress.setVisible(False)
                    self.completed_progress = 0
                    self.total_progress = 0
                else:
                    self.progress.setRange(0, self.total_progress)
                    self.progress.setValue(min(self.completed_progress, self.total_progress))

            self.log(f"[INFO] 任务 {sid} 已取消")

        if self.current and self.current[0] == sid:
            self._update_download_buttons(sid)

    def closeEvent(self, event):
        sids = list(self.active_downloads.keys())
        for sid in sids:
            self._cancel_download(sid)
        super().closeEvent(event)

    def _append_page(self, servants):
        added = False
        for s in servants:
            sid = s[0]
            if sid in self._seen_sids:
                continue
            self._seen_sids.add(sid)
            if sid in TIAMAT_IDS:
                self._tiamats.append(s)  # 前段：三位提亚马特，保持出现顺序
            else:
                self._others.append(s)  # 后段：其余从者，按到达顺序累积
            added = True
        if added:
            self._rebuild_and_refresh()

    def _on_sort_changed(self, text: str):
        self._sort_order = "asc" if "升" in text else "desc"
        self._rebuild_and_refresh()

    def _rebuild_and_refresh(self):
        if self._sort_order in (None, ""):
            combined = self._tiamats + self._others  # 加载阶段：严格“到达顺序”，不排序
        else:
            reverse = (self._sort_order == "desc")
            combined = self._tiamats + sorted(self._others, key=lambda s: int(s[0]), reverse=reverse)
        self.servants = combined
        self._on_search_changed(self.search_edit.text())
